import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:location_test/app/domain/models/ble_beacon.dart';
import 'package:permission_handler/permission_handler.dart';

class BluetoothService {
  final StreamController<List<BleBeacon>> _beaconStreamController =
      StreamController<List<BleBeacon>>.broadcast();
  StreamSubscription<List<ScanResult>>? _scanSubscription;
  bool _isScanning = false;

  Stream<List<BleBeacon>> get beaconStream => _beaconStreamController.stream;

  Future<void> startScanning() async {
    if (_isScanning) return;

    await _ensurePermissions();
    await _ensureBluetoothEnabled();

    await FlutterBluePlus.startScan(
      continuousUpdates: true,
      oneByOne: true,
      androidUsesFineLocation: true,
    );
    _isScanning = true;

    _scanSubscription = FlutterBluePlus.onScanResults.listen(
      (results) {
        final DateTime now = DateTime.now();
        final List<BleBeacon> parsed = [];

        for (final ScanResult result in results) {
          final List<BleBeacon> beacons =
              _parseScanResult(result, timestamp: now);
          if (beacons.isNotEmpty) {
            parsed.addAll(beacons);
          }
        }

        if (parsed.isNotEmpty) {
          _beaconStreamController.add(parsed);
        }
      },
      onError: (error, stackTrace) {
        _beaconStreamController.addError(error, stackTrace);
      },
    );
  }

  Future<void> stopScanning() async {
    if (!_isScanning) return;

    await FlutterBluePlus.stopScan();
    await _scanSubscription?.cancel();
    _scanSubscription = null;
    _isScanning = false;
    _beaconStreamController.add(const <BleBeacon>[]);
  }

  Future<void> dispose() async {
    await stopScanning();
    await _beaconStreamController.close();
  }

  Future<void> _ensurePermissions() async {
    final List<Permission> permissions = [];

    if (Platform.isAndroid) {
      permissions.addAll([
        Permission.bluetooth,
        Permission.bluetoothScan,
        Permission.bluetoothConnect,
        Permission.locationWhenInUse,
      ]);
    } else if (Platform.isIOS) {
      permissions.addAll([
        Permission.bluetooth,
        Permission.locationWhenInUse,
      ]);
    }

    for (final Permission permission in permissions) {
      final PermissionStatus status = await permission.request();
      if (!status.isGranted) {
        throw Exception('蓝牙权限被拒绝: $permission');
      }
    }
  }

  Future<void> _ensureBluetoothEnabled() async {
    BluetoothAdapterState state = FlutterBluePlus.adapterStateNow;

    if (state != BluetoothAdapterState.on) {
      try {
        state = await FlutterBluePlus.adapterState
            .firstWhere((s) => s != BluetoothAdapterState.unknown);
      } catch (_) {
        state = FlutterBluePlus.adapterStateNow;
      }
    }

    if (state != BluetoothAdapterState.on) {
      if (Platform.isAndroid) {
        try {
          await FlutterBluePlus.turnOn(timeout: 15);
        } catch (_) {
          // ignore
        }
        state = FlutterBluePlus.adapterStateNow;
      }

      if (state != BluetoothAdapterState.on) {
        throw Exception('蓝牙未开启，请先开启蓝牙后再试');
      }
    }
  }

  List<BleBeacon> _parseScanResult(
    ScanResult result, {
    required DateTime timestamp,
  }) {
    final AdvertisementData data = result.advertisementData;

    final List<BleBeacon>? jsonPayload =
        _tryParseJsonBroadcast(data, timestamp: timestamp);
    if (jsonPayload != null && jsonPayload.isNotEmpty) {
      return jsonPayload;
    }

    final BleBeacon? beacon =
        _parseIBeacon(result, timestamp: timestamp);
    if (beacon != null) {
      return [beacon];
    }

    return const <BleBeacon>[];
  }

  List<BleBeacon>? _tryParseJsonBroadcast(
    AdvertisementData data, {
    required DateTime timestamp,
  }) {
    final Iterable<String> candidates = _extractPossibleJsonPayloads(data);

    for (final String candidate in candidates) {
      final String? jsonString = _sanitizeJson(candidate);
      if (jsonString == null) continue;

      try {
        final dynamic decoded = json.decode(jsonString);
        if (decoded is! Map<String, dynamic>) continue;

        final dynamic lbRaw = decoded['lb'];
        if (lbRaw is! List) continue;

        final List<BleBeacon> parsed = [];
        for (final dynamic raw in lbRaw) {
          if (raw is! Map) continue;

          final int? major = _valueToInt(raw['ma']);
          final int? minor = _valueToInt(raw['mi']);
          final int? rssi = _valueToInt(raw['rs']);

          if (major == null || minor == null || rssi == null) {
            continue;
          }

          parsed.add(
            BleBeacon(
              major: major,
              minor: minor,
              rssi: rssi.abs(),
              timestamp: timestamp,
            ),
          );
        }

        if (parsed.isNotEmpty) {
          return parsed;
        }
      } catch (_) {
        continue;
      }
    }

    return null;
  }

  Iterable<String> _extractPossibleJsonPayloads(AdvertisementData data) sync* {
    for (final List<int> bytes in data.manufacturerData.values) {
      final String? candidate = _bytesToString(bytes);
      if (candidate != null) yield candidate;
    }

    for (final List<int> bytes in data.serviceData.values) {
      final String? candidate = _bytesToString(bytes);
      if (candidate != null) yield candidate;
    }

    final String advName = data.advName;
    if (advName.isNotEmpty) {
      yield advName;
    }
  }

  String? _bytesToString(List<int> bytes) {
    if (bytes.isEmpty) return null;
    try {
      return utf8.decode(bytes, allowMalformed: true);
    } catch (_) {
      return null;
    }
  }

  String? _sanitizeJson(String source) {
    final int start = source.indexOf('{');
    final int end = source.lastIndexOf('}');
    if (start == -1 || end == -1 || end <= start) {
      return null;
    }
    final String candidate = source.substring(start, end + 1);
    if (!candidate.contains('"lb"')) {
      return null;
    }
    return candidate;
  }

  BleBeacon? _parseIBeacon(
    ScanResult result, {
    required DateTime timestamp,
  }) {
    final AdvertisementData data = result.advertisementData;

    for (final List<int> raw in data.manufacturerData.values) {
      if (raw.length < 6) continue;

      final Uint8List bytes = Uint8List.fromList(raw);
      final int majorIndex = bytes.length - 5;
      final int minorIndex = bytes.length - 3;
      if (majorIndex < 0 || minorIndex < 0) continue;

      final ByteData view = ByteData.sublistView(bytes);
      final int major = view.getUint16(majorIndex, Endian.big);
      final int minor = view.getUint16(minorIndex, Endian.big);

      return BleBeacon(
        major: major,
        minor: minor,
        rssi: result.rssi.abs(),
        timestamp: timestamp,
      );
    }

    return null;
  }

  int? _valueToInt(dynamic value) {
    if (value == null) return null;
    if (value is int) return value;
    if (value is double) return value.round();
    if (value is String) return int.tryParse(value);
    return null;
  }
}
